package mms.tooleditor;

import com.mxgraph.io.mxCodec;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxICell;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.util.mxGraphTransferable;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource.mxIEventListener;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
import mms.metamodel.Entity;
import mms.metamodel.Property;
import org.w3c.dom.Document;

import javax.swing.*;
import java.awt.*;
import java.net.URL;
import java.text.NumberFormat;
import java.util.Iterator;
import java.util.List;

public class MetaToolGraphEditor extends BasicGraphEditor {
    /**
     *
     */
    private static final long serialVersionUID = -4601740824088314699L;

    /**
     * Holds the shared number formatter.
     *
     * @see java.text.NumberFormat#getInstance()
     */
    public static final NumberFormat numberFormat = NumberFormat.getInstance();

    /**
     * Holds the URL for the icon to be used as a handle for creating new
     * connections. This is currently unused.
     */
    public static URL url = null;

    //MetaTool.class.getResource("/mms/images/connector.gif");

    public MetaToolGraphEditor() {
        this("mxGraph Editor", new CustomGraphComponent(new CustomGraph()));
    }

    /**
     *
     */
    public MetaToolGraphEditor(String appTitle, mxGraphComponent component) {
        super(appTitle, component);
        final mxGraph graph = component.getGraph();

        // Creates the shapes palette
        EditorPalette shapesPalette = insertPalette("Language");
//		EditorPalette imagesPalette = insertPalette(mxResources.get("images"));
//		EditorPalette symbolsPalette = insertPalette(mxResources.get("symbols"));

        // Sets the edge template to be used for creating new edges if an edge
        // is clicked in the shape palette
        shapesPalette.addListener(mxEvent.SELECT, new mxIEventListener() {
            public void invoke(Object sender, mxEventObject evt) {
                Object tmp = evt.getProperty("transferable");

                if (tmp instanceof mxGraphTransferable) {
                    mxGraphTransferable t = (mxGraphTransferable) tmp;
                    Object cell = t.getCells()[0];

                    if (graph.getModel().isEdge(cell)) {
                        ((CustomGraph) graph).setEdgeTemplate(cell);
                    }
                }
            }

        });

        // Adds some template cells for dropping into the graph
        shapesPalette
                .addTemplate(
                        "Entity",
                        new ImageIcon(
                                MetaToolGraphEditor.class
                                        .getResource("/mms/images/entity.png")),
                        "entityElement", 160, 120, "");
/*
        shapesPalette
				.addTemplate(
						"Horizontal Line",
						new ImageIcon(
								MetaToolGraphEditor.class
										.getResource("/mms/images/hline.png")),
						"line", 160, 10, "");
		shapesPalette
				.addTemplate(
						"Actor",
						new ImageIcon(
								MetaToolGraphEditor.class
										.getResource("/mms/images/actor.png")),
						"shape=actor", 120, 160, "");
		shapesPalette
				.addEdgeTemplate(
						"Straight",
						new ImageIcon(
								MetaToolGraphEditor.class
										.getResource("/mms/images/straight.png")),
						"straight", 120, 120, "");
*/
        shapesPalette
                .addEdgeTemplate(
                        "Composition",
                        new ImageIcon(
                                MetaToolGraphEditor.class
                                        .getResource("/mms/images/composition.png")),
                        "composition", 100, 100, "");
        shapesPalette
                .addEdgeTemplate(
                        "Aggregation",
                        new ImageIcon(
                                MetaToolGraphEditor.class
                                        .getResource("/mms/images/aggregation.png")),
                        "aggregation", 100, 100, "");
/*
        shapesPalette
				.addEdgeTemplate(
						"Arrow",
						new ImageIcon(
								MetaToolGraphEditor.class
										.getResource("/mms/images/arrow.png")),
						"arrow", 120, 120, "");

*/
    }

    /**
     *
     */
    public static class CustomGraphComponent extends mxGraphComponent {

        /**
         *
         */
        private static final long serialVersionUID = -6833603133512882012L;

        /**
         * @param graph
         */
        public CustomGraphComponent(mxGraph graph) {
            super(graph);

            // Sets switches typically used in an tooleditor
            setPageVisible(true);
            setGridVisible(true);
            setToolTips(true);
            getConnectionHandler().setCreateTarget(true);

            // Loads the defalt stylesheet from an external file
            mxCodec codec = new mxCodec();
            Document doc = mxUtils.loadDocument(MetaToolGraphEditor.class.getResource(
                    "/mms/resources/default-style.xml")
                    .toString());
            codec.decode(doc.getDocumentElement(), graph.getStylesheet());

            // Sets the background to white
            getViewport().setOpaque(true);
            getViewport().setBackground(Color.WHITE);
        }

        /**
         * Overrides drop behaviour to set the cell style if the target
         * is not a valid drop target and the cells are of the same
         * type (eg. both vertices or both edges).
         */
        public Object[] importCells(Object[] cells, double dx, double dy,
                                    Object target, Point location) {
            if (target == null && cells.length == 1 && location != null) {
                target = getCellAt(location.x, location.y);

                if (target instanceof mxICell && cells[0] instanceof mxICell) {
                    mxICell targetCell = (mxICell) target;
                    mxICell dropCell = (mxICell) cells[0];

                    if (targetCell.isVertex() == dropCell.isVertex()
                            || targetCell.isEdge() == dropCell.isEdge()) {
                        mxIGraphModel model = graph.getModel();
                        model.setStyle(target, model.getStyle(cells[0]));
                        graph.setSelectionCell(target);

                        return null;
                    }
                }
            }

            return super.importCells(cells, dx, dy, target, location);
        }

    }

    /**
     * A graph that creates new edges from a given template edge.
     */
    public static class CustomGraph extends mxGraph {
        /**
         * Holds the edge to be used as a template for inserting new edges.
         */
        protected Object edgeTemplate;

        /**
         * Custom graph that defines the alternate edge style to be used when
         * the middle control point of edges is double clicked (flipped).
         */
        public CustomGraph() {
            setAlternateEdgeStyle("edgeStyle=mxEdgeStyle.ElbowConnector;elbow=vertical");

            addListener(mxEvent.CELLS_ADDED, new mxIEventListener() {
                public void invoke(Object sender, mxEventObject evt) {
                    mxCell cell = (mxCell) ((Object[]) evt.getProperty("cells"))[0];
                    if (cell.getStyle().equals("entityElement")) {
                        Entity entity = new Entity();
                        entity.setName("Class1");
                        entity.setProperties(new Property[]{new Property("name", "String"), new Property("id", "int")});
                        cell.setValue(entity);
                    }
                }
            });

        }

        /**
         * Sets the edge template to be used to inserting edges.
         */
        public void setEdgeTemplate(Object template) {
            edgeTemplate = template;
        }

        /**
         * Prints out some useful information about the cell in the tooltip.
         */
        public String getToolTipForCell(Object cell) {
            String tip = "<html>";
            mxGeometry geo = getModel().getGeometry(cell);
            mxCellState state = getView().getState(cell);

            if (getModel().isEdge(cell)) {
                tip += "points={";

                if (geo != null) {
                    List<mxPoint> points = geo.getPoints();

                    if (points != null) {
                        Iterator<mxPoint> it = points.iterator();

                        while (it.hasNext()) {
                            mxPoint point = it.next();
                            tip += "[x=" + numberFormat.format(point.getX())
                                    + ",y=" + numberFormat.format(point.getY())
                                    + "],";
                        }

                        tip = tip.substring(0, tip.length() - 1);
                    }
                }

                tip += "}<br>";
                tip += "absPoints={";

                if (state != null) {

                    for (int i = 0; i < state.getAbsolutePointCount(); i++) {
                        mxPoint point = state.getAbsolutePoint(i);
                        tip += "[x=" + numberFormat.format(point.getX())
                                + ",y=" + numberFormat.format(point.getY())
                                + "],";
                    }

                    tip = tip.substring(0, tip.length() - 1);
                }

                tip += "}";
            } else {
                tip += "geo=[";

                if (geo != null) {
                    tip += "x=" + numberFormat.format(geo.getX()) + ",y="
                            + numberFormat.format(geo.getY()) + ",width="
                            + numberFormat.format(geo.getWidth()) + ",height="
                            + numberFormat.format(geo.getHeight());
                }

                tip += "]<br>";
                tip += "state=[";

                if (state != null) {
                    tip += "x=" + numberFormat.format(state.getX()) + ",y="
                            + numberFormat.format(state.getY()) + ",width="
                            + numberFormat.format(state.getWidth())
                            + ",height="
                            + numberFormat.format(state.getHeight());
                }

                tip += "]";
            }

            mxPoint trans = getView().getTranslate();

            tip += "<br>scale=" + numberFormat.format(getView().getScale())
                    + ", translate=[x=" + numberFormat.format(trans.getX())
                    + ",y=" + numberFormat.format(trans.getY()) + "]";
            tip += "</html>";

            return tip;
        }

/*
        @Override
        public Object createVertex(Object parent, String id, Object value,
                                   double x, double y, double width, double height, String style,
                                   boolean relative) {
            if (style.equals("entityElement")) {
                Entity entity= new Entity();
                entity.setName("Class1");
                entity.setProperties(new Property[]{new Property("name","String"), new Property("id","int")});
            }
            return super.createVertex(parent, id, value, x, y, width, height, style, relative);
        }
*/

        /**
         * Overrides the method to use the currently selected edge template for
         * new edges.
         *
         * @param parent
         * @param id
         * @param value
         * @param source
         * @param target
         * @param style
         * @return
         */
        public Object createEdge(Object parent, String id, Object value,
                                 Object source, Object target, String style) {
            if (edgeTemplate != null) {
                mxCell edge = (mxCell) cloneCells(new Object[]{edgeTemplate})[0];
                edge.setId(id);
/*
                switch (edge.getStyle()) {
                    "composition" : edge.setValue();
                }
*/

                return edge;
            }

            return super.createEdge(parent, id, value, source, target, style);
        }

        // Overrides method to provide a cell label in the display
        public String convertValueToString(Object cell) {
            if (cell instanceof mxCell) {
                Object value = ((mxCell) cell).getValue();

                if (value instanceof Entity) {
                    Entity entity = (Entity) value;
                    return entity.getName() + "||| " + entity.getProperties()[0] + ", " + entity.getProperties()[1];
                }
            }

            return super.convertValueToString(cell);
        }

    }

}
